#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sobjc-commons/defines.h>
#include <sobjc-commons/utils.h>
#include "codegen.h"

static objc_codegen_t make_codegen();

#define INDENT          "  "
#define LF              "\n"
#define TERM            ";"

static objc_codegen_t
make_codegen()
{
  objc_codegen_t self;

  self = ALLOC(struct objc_codegen);
  self->path = NULL;
  self->fp = NULL;
  self->indent_lv = 0;

  return self;
}

objc_codegen_t
objc_make_codegen(const char *path)
{
  objc_codegen_t self;

  self = make_codegen();
  self->path = objc_strdup(path);
  self->fp = fopen(path, "w");

  return self;
}

void
objc_free_codegen(objc_codegen_t self)
{
  objc_codegen_close(self);
  if (self->path != NULL)
    objc_free(self->path);
  objc_free(self);
}

void
objc_codegen_close(objc_codegen_t self)
{
  if (self->fp != NULL)
    fclose(self->fp);
}

void
objc_codegen_indent(objc_codegen_t self)
{
  self->indent_lv++;
}

void
objc_codegen_dedent(objc_codegen_t self)
{
  self->indent_lv--;
}

void
objc_codegen_write(objc_codegen_t self, const char *s)
{
  fwrite(s, 1, strlen(s), self->fp);
}

void
objc_codegen_writef(objc_codegen_t self, const char *s, ...)
{
  va_list ap;

  va_start(ap, s);
  vfprintf(self->fp, s, ap);
  va_end(ap);
}

void
objc_codegen_writeln(objc_codegen_t self)
{
  fwrite(LF, 1, 1, self->fp);
}

void
objc_codegen_writelnf(objc_codegen_t self, const char *s, ...)
{
  va_list ap;

  va_start(ap, s);
  vfprintf(self->fp, s, ap);
  va_end(ap);
  fprintf(self->fp, LF);
}

void
objc_codegen_writelns(objc_codegen_t self, int n)
{
  static char *lines = NULL;
  static int nlines = 0;
  int i;

  if (lines == NULL)
    lines = ALLOC_N(char, n+1);
  if (nlines < n) {
    nlines = n;
    REALLOC_N(lines, char, n+1);
  }

  for (i = 0; i < n; i++)
    lines[i] = '\n';
  lines[i] = 0;
  objc_codegen_write(self, lines);
}

void
objc_codegen_write_line_mark(objc_codegen_t self, objc_line_mark_t mark)
{
  if (mark->prev != NULL)
    objc_codegen_write_line_mark(self, mark->prev);

  objc_codegen_writef(self, "# %d \"%s\" ", mark->lineno, mark->file);
  if (mark->flags & OBJC_LINE_MARK_BEGIN)
    objc_codegen_write(self, "1 ");
  if (mark->flags & OBJC_LINE_MARK_END)
    objc_codegen_write(self, "2 ");
  if (mark->flags & OBJC_LINE_MARK_SYSTEM)
    objc_codegen_write(self, "3 ");
  if (mark->flags & OBJC_LINE_MARK_SRC)
    objc_codegen_write(self, "4");
  objc_codegen_writeln(self);
}

void
objc_codegen_write_indent(objc_codegen_t self)
{
  fprintf(self->fp, INDENT);
}

void
objc_codegen_write_comment(objc_codegen_t self, const char *format, ...)
{
  va_list ap;

  objc_codegen_write_indent(self);
  va_start(ap, format);
  fprintf(self->fp, "/* ");
  vfprintf(self->fp, format, ap);
  fprintf(self->fp, " */");
  va_end(ap);
  fprintf(self->fp, TERM LF);
}

void
objc_codegen_write_stmt(objc_codegen_t self, const char *s, ...)
{
  va_list ap;

  va_start(ap, s);
  vfprintf(self->fp, s, ap);
  va_end(ap);
  fprintf(self->fp, TERM LF);
}

void
objc_codegen_write_gcc_attr(objc_codegen_t self, const char *s, ...)
{
  va_list ap;

  fprintf(self->fp, "__attribute__((");
  va_start(ap, s);
  vfprintf(self->fp, s, ap);
  va_end(ap);
  fprintf(self->fp, "))");
}

